import { extend } from '../shared';
let activeEffect;
let shouldTrack;
class ReactiveEffect {
	private _fn: any;
	deps = [];
	// active含义：是否是stop状态（true -> 不是/false -> 是）
	active = true;
	onStop?: () => void;
	constructor(fn, public scheduler?) {
		this._fn = fn;
	}

	run() {
		// 如果是stop状态
		if (!this.active) {
			return this._fn();
		}
		shouldTrack = true;
		// 运行副作用函数时，收集依赖
		activeEffect = this;
		const result = this._fn();
		shouldTrack = false;
		return result;
	}
	stop() {
		// active含义：是否是stop状态，
		// true表示依赖还未被清除还在（处于被收集状态）非stop状态
		// false表示依赖已经被清除了（处于stop状态）
		if (this.active) {
			// 清除副作用
			cleanupEffect(this);
			if (this.onStop) {
				this.onStop();
			}
			this.active = false;
		}
	}
}
function cleanupEffect(effect) {
	effect.deps.forEach((dep: any) => {
		dep.delete(effect);
	});
	effect.deps.length = 0;
}

/**
 * 依赖收集
 * @param target
 * @param key
 */
let targetMap = new Map();
export function track(target, key) {
	if (!isTracking()) return;
	// target -> key -> dep
	let depsMap = targetMap.get(target);
	if (!depsMap) {
		depsMap = new Map();
		targetMap.set(target, depsMap);
	}
	let dep = depsMap.get(key);

	if (!dep) {
		dep = new Set();
		depsMap.set(key, dep);
	}
	// if(!activeEffect) return;
	// if(!shouldTrack) return;

	// 如果同一个依赖已经被收集了，就不需要重复收集了
	if (dep.has(activeEffect)) return;
	dep.add(activeEffect);
	// 反向收集dep
	activeEffect.deps.push(dep);
}

export function trigger(target, key) {
	let depsMap = targetMap.get(target);
	if (depsMap) {
		let dep = depsMap.get(key);
		dep &&
			dep.forEach(effect => {
				if (effect.scheduler) {
					effect.scheduler();
				} else {
					effect.run();
				}
			});
	}
}

// 返回值为是否在收集依赖状态
function isTracking() {
	return shouldTrack && activeEffect !== undefined;
}

export function effect(fn, options: any = {}) {
	const _effect = new ReactiveEffect(fn, options.scheduler);
	// _effect.onStop = options.onStop;
	// 优化写法
	// Object.assign(_effect, options);
	// shared
	extend(_effect, options);
	_effect.run();
	const runner: any = _effect.run.bind(_effect);
	runner.effect = _effect;
	return runner;
}

export function stop(runner) {
	runner.effect.stop();
}
